package de.recipeminer.ui;


import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import de.recipeminer.environments.ExecutionContextProvider;
import de.recipeminer.tools.LimitablePrintStream;
import de.recipeminer.ui.commands.CommandRegistry;
import de.recipeminer.ui.commands.UserCommand;
import de.recipeminer.ui.parsing.OptionParser;
import joptsimple.OptionException;
import joptsimple.OptionSet;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.util.List;

import static java.util.Arrays.asList;


/**
 * Application entry point if this programm is used with the command line. Parses
 * program options and arguments, creates a suitable Command objects and runs it
 * to perform the action requested by the user.
 *
 * @autor: Markus Ackermann
 */
public class ConsoleInterface {
    /** parser for options common to all invocations */
    private static Logger logger = Logger.getLogger(ConsoleInterface.class);
    /** knows all possible options of all commands for first successful parse to determine
     * which specific command ist requested*/
    private OptionParser basicParser, aggregatedParser;
    public static final String USAGE_LINE_TEMPLATE = 
            "Usage: java -jar recipeminer.jar [GENERIC OPTIONS] %s [%s]";
    private static final String COMMAND_HELP_HINT =
            "Command description: java -jar recipeminer.jar --help [COMMAND]";
    

    private static final String OPTIONS_INTRO = "Available generic options:";
    private static final String GENERIC_COMMAND = "COMMAND";
    private static final String GENERIC_COMMAND_OPTIONS = "COMMAND OPTIONS";
    
    

    public ConsoleInterface() {
        basicParser = createBasicParser();
        aggregatedParser = aggregatePossibleOptions(createBasicParser());
    }

    /**
     * Defines accepted command line options and parses the command line argument array 
     * into a {@link OptionSet} representing activated options and non-option arguments.*/
    public OptionSet parseOptions(String[] args) throws IOException {
        OptionParser parser = createBasicParser();

        return parser.parse(args);
    }
    /**
     * Application entry point if this programm is used with the command line.
     *
     * @param args Strings given to the application as command line arguments
     */
    public static void main(String[] args) throws IOException {
        ConsoleInterface ci = new ConsoleInterface();
        CommandRegistry cr = CommandRegistry.getInstance();
        OptionSet aggregatedOptions = null;
        try {
            aggregatedOptions = ci.aggregatedParser.parse(args);
        } catch (OptionException oe) {
            exitOnOptionError(ci, oe.getMessage());
        }

        logger.trace(Joiner.on('\n').join(aggregatedOptions.specs()));
        
        String requestedCommand = ci.extractRequestedCommand(aggregatedOptions);
        Optional<UserCommand> command = cr.getCommand(requestedCommand);
        if(aggregatedOptions.has("h") && requestedCommand == null){
            System.out.println(ci.genericHelpString());
            System.exit(0);
        }
          if(!command.isPresent()) {
                String errorMsg = "Error: No command given.";
                exitOnOptionError(ci, errorMsg);
            }

        if(! cr.getKnownCommandNames().contains(requestedCommand)) {
            String errorMsg = "Error: " + new OptionParser.CommandNameUnknownException(requestedCommand)
                    .getMessage();
            exitOnOptionError(ci, errorMsg);
        } else if (aggregatedOptions.has("h")) {
            System.out.println(command.orNull().specificHelpString());
            System.exit(0);
        }
    }

    public static OptionParser createBasicParser() {
                 OptionParser parser = new OptionParser();
                 parser.acceptsAll(asList("h", "?", "help"), "Shows this help screen");
                parser.acceptsAll(asList("e", "env"), "Select specific exeuction environment")
                    .withRequiredArg().ofType(String.class).defaultsTo("production")
                    .describedAs("Definition name defined in the environemt config file.");
                parser.acceptsAll(asList("c", "config"), "Set file to configure execution environments.")
                    .withRequiredArg().ofType(File.class)
                    .defaultsTo(new File[]{new File("config/environments")});
                parser.acceptsAll( asList("l", "limit"),"Limit the lines to the output. 0 for no limit.")
                    .withRequiredArg().ofType(Integer.class).defaultsTo(500);
        return parser;
    }

    LimitablePrintStream initOutputStream(OptionSet mergedOpts) {

        if (mergedOpts.has("l") && (Integer) mergedOpts.valueOf("l") > 0) {
            return new LimitablePrintStream((Integer) mergedOpts.valueOf("l"));
        } else {
            return new LimitablePrintStream();
        }
    }

    void setEnvironment(OptionSet mergedOpts) {
        if (mergedOpts.has("c") && ((String) mergedOpts.valueOf("c")).length() > 0 ) {
            String configFile = (String) mergedOpts.valueOf("c");
            ExecutionContextProvider.init(configFile);
        }

        if (mergedOpts.has("e") && ((String) mergedOpts.valueOf("e")).length() > 0 ) {
            String contextName = (String) mergedOpts.valueOf("e");
            ExecutionContextProvider.getInstance().selectContext(contextName);
        }

    }

    private static void exitOnOptionError(ConsoleInterface ci, String errorMsg) {
        System.out.println(Joiner.on('\n').join(errorMsg, "", ci.genericHelpString()));
        System.exit(1);
    }

    protected String extractRequestedCommand(OptionSet opts) {
        List<String> cmdArgs = opts.nonOptionArguments();
        return cmdArgs.size() == 0 ? null : cmdArgs.get(0);
    }
    
    protected String availableCommandsLine() {
        ImmutableSet<String> names = CommandRegistry.getInstance().getKnownCommandNames();
        String nameString = Joiner.on(' ').join(names);
        return Joiner.on(' ').join("Available commands:", nameString);
    }
    
    protected String genericHelpString() {
        String usageLine = String.format(USAGE_LINE_TEMPLATE, GENERIC_COMMAND, GENERIC_COMMAND_OPTIONS);
        return Joiner.on('\n').join(usageLine,"",availableCommandsLine(), COMMAND_HELP_HINT,"",
                                    OPTIONS_INTRO,basicParser.getHelpText());
    }

    private OptionParser aggregatePossibleOptions(de.recipeminer.ui.parsing.OptionParser baseParser) {
        for(UserCommand command: CommandRegistry.getInstance().getKnownCommands()) {
            command.extendOptions(baseParser);
        }
        // parser now know also all options possible by known commands
        return baseParser;
    }
}
